package com.qianfeng.smartplatform.websocket.handler;


//
//                            _ooOoo_  
//                           o8888888o  
//                           88" . "88  
//                           (| -_- |)  
//                            O\ = /O  
//                        ____/`---'\____  
//                      .   ' \\| |// `.  
//                       / \\||| : |||// \  
//                     / _||||| -:- |||||- \  
//                       | | \\\ - /// | |  
//                     | \_| ''\---/'' | |  
//                      \ .-\__ `-` ___/-. /  
//                   ___`. .' /--.--\ `. . __  
//                ."" '< `.___\_<|>_/___.' >'"".  
//               | | : `- \`.;`\ _ /`;.`/ - ` : | |  
//                 \ \ `-. \_ __\ /__ _/ .-` / /  
//         ======`-.____`-.___\_____/___.-`____.-'======  
//                            `=---='  
//  
//         .............................................  
//                  佛祖镇楼            BUG辟易  
//          佛曰:  
//                  写字楼里写字间，写字间里程序员；  
//                  程序人员写程序，又拿程序换酒钱。  
//                  酒醒只在网上坐，酒醉还来网下眠；  
//                  酒醉酒醒日复日，网上网下年复年。  
//                  但愿老死电脑间，不愿鞠躬老板前；  
//                  奔驰宝马贵者趣，公交自行程序员。  
//                  别人笑我忒疯癫，我笑自己命太贱；  


import com.qianfeng.smartplatform.event.CheckStatusEvent;
import com.qianfeng.smartplatform.event.DevicePowerEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Created by jackiechan on 2021/12/22 09:26
 * 当前类的对象是单例对象
 *
 * @author jackiechan
 */
@Component
@Lazy(false)
public abstract class MyBaseWebSocketHandler extends TextWebSocketHandler {
    Logger logger = LoggerFactory.getLogger(this.getClass());

    //用于保存所有会话的集合,key 是唯一表示, value 是会话
    private Map<String, WebSocketSession> allClients = new ConcurrentHashMap<>();

    private ConcurrentHashMap<String, Long> updateTimes = new ConcurrentHashMap();//保存设备最后的会话时间

    @Autowired
    private ApplicationContext context;

    /**
     * 当客户端和服务端建立连接的时候,就相当于你登录 QQ 成功,服务器必须保存你的会话数据,不然给你发送数据的时候就找不到你了,每个建立连接的都需要保存
     *
     * @param session 当前正在会话的人
     * @throws Exception
     */
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        //保存会话
        //1 保存在什么地方,因为会有很多人建立连接,所以一定要用集合,无非就是键值对类型的集合还是纯值的集合
        //2 需要的时候如何找,我们发现后面需要根据收的人来查找会话, 所以一定不能 list 这种不区分的集合,否则需要挨个遍历,更适合用 key-value 这样的键值对集合比如 map
        // 我们保存的时候需要一个 key,这个 key 必须是唯一的, 而且每个会话对应的 key 还不能变,因为 key 变了就找不到数据了
        // 我们每个用户都有用户名或者 id,就像 QQ 的 QQ 号是唯一的,我们可以使用这个唯一的标识来作为 key,如何知道当前的会话对应的是什么 key
        //websocket中可以通过请求路径来传递一个唯一标识过来 比如 websocket 的连接地址是 /websocket 可以在后加/12345 来声明这是 12345
        String uuid = (String) session.getAttributes().get("name");
        updateTimes.put(uuid, System.currentTimeMillis());
        //其他操作,比如像 QQ 登录后获取还没接收的消息,但是这是具体的业务了
        //更新设备为在线
        if (isNeedUpdateStatus()) {
            logger.error("{}更新设备{}在线信息", getClass().getName(), uuid);
            context.publishEvent(DevicePowerEvent.sendOpen(uuid));
        }
        //如果当前客户端之前连接过,则先关闭并且移除
        WebSocketSession socketSession = allClients.get(uuid);

        
        try {
            if (socketSession != null) {
                allClients.remove(uuid);
                if (socketSession.isOpen()) {
                    socketSession.close();
                }
            }
        }catch (Exception e){
           e.printStackTrace();
        }
        allClients.put(uuid, session);//根据之前拦截保存的唯一表示来保存会话

    }

    /**
     * 处理文本消息的,相当于你在给你的女神发送消息
     *
     * @param session 会话,发送数据到服务器的会话是谁
     * @param message 消息的内容,在聊天的情况下,只有发送者和接收者不一样,其他的的东西都是一样的,张三给李四发消息和王五给赵六发送消息是一样,所以方法就这一个,那额如何区分接收者,肯定需要在消息的内容中来处理
     * @throws Exception
     */
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        String content = message.getPayload();//这个获取到的数据发送过来的内容,注意这个内容并不是单纯的只是聊天的内容
        // System.err.println("客户端的消息" + content);
        logger.error("{}收到设备的消息内容:{}", getClass().getName(), content);
        String uuid = (String) session.getAttributes().get("name");
        updateTimes.put(uuid, System.currentTimeMillis());
        if ("ping".equals(content)) {
            session.sendMessage(new TextMessage("pong"));
        }
        WebSocketSession socketSession = allClients.get(uuid);
        if (socketSession == null) {
            allClients.put(uuid, session);
            if (isNeedUpdateStatus()) {
                logger.error("{}更新设备{}在线信息", getClass().getName(), uuid);
                context.publishEvent(DevicePowerEvent.sendOpen(uuid));
            }

        }
    }

    /**
     * 客户端和服务器连接关闭了执行当前方法,相当于你退出了 QQ,这时候实际上要关闭会话连接,因为你已经断开,如果我不删除保存的数据的话,我能够查询继续给你发送消息,但是实际上是发布过去的
     *
     * @param session
     * @param status
     * @throws Exception
     */
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        if (session != null) {
            String uuid = (String) session.getAttributes().get("name");
            WebSocketSession webSocketSession = allClients.get(uuid);
            if (session == webSocketSession) {
                allClients.remove(uuid);
                updateTimes.remove(uuid);
                //更新在线状态为关闭,但是因为存在多个websocket,对同一个设备进行了不同操作,有的是不需要执行命令的,所以需要根据具体情况来执行
                //解决方案,可以通过定义一个boolean类型的方法让子类重写,在此处调用该方法,返回true执行,返回false的不执行 ,第二种方案,让子类重写当前方法,在super之后执行对应操作
                if (isNeedUpdateStatus()) {
                    logger.error("{}更新设备{}在线信息", getClass().getName(), uuid);
                    context.publishEvent(DevicePowerEvent.sendClose(uuid));
                }
            }


        }
        super.afterConnectionClosed(session, status);
    }

    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
        if (session != null) {
            String uuid = (String) session.getAttributes().get("name");
            allClients.remove(uuid);
            if (isNeedUpdateStatus()) {
                context.publishEvent(DevicePowerEvent.sendClose(uuid));
            }

        }

        super.handleTransportError(session, exception);
    }

    public int sendControl(String uuid, String command) throws IOException {
        WebSocketSession session = allClients.get(uuid);
        if (session != null && session.isOpen()) {
            session.sendMessage(new TextMessage(command));
            return 1;
        }
        return 0;
    }


    /**
     * 收到事件的时候进行客户端在线检查
     *
     * @param event
     * @throws IOException
     */
    @EventListener
    public void onStatusEvent(CheckStatusEvent event) throws IOException {
        logger.error("{}批量更新设备在线信息", getClass().getName());
//        allClients.forEach((key,client)->{
//            try {
//                client.sendMessage(new TextMessage(""));
//            }catch (Exception e){
//                e.printStackTrace();
//                //不在线关闭连接
//                logger.trace("{}设备:{} 离线了",getClass().getName(),key);
//                allClients.remove(key);
//                //更新在线状态
//                if (isNeedUpdateStatus()) {
//                    context.publishEvent(DevicePowerEvent.sendClose((String) client.getAttributes().get("name")));
//                }
//
//            }
//        });

        updateTimes.forEach((uuid, time) -> {
            //time 是最后一次更新时间
            if (System.currentTimeMillis() - time > 60000) {
                System.err.println("设备" + uuid + "已经离线了");
                WebSocketSession webSocketSession = allClients.get(uuid);
                allClients.remove(uuid);
                updateTimes.remove(uuid);
                //发送更新状态的事件

                if (webSocketSession != null && webSocketSession.isOpen()) {
                    try {
                        webSocketSession.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }

        });
    }

//
//    /**
//     * 更新单个设备的状态
//     *
//     * @param device
//     * @throws IOException
//     */
//    @EventListener
//    public void onUpdateDeviceStatus(Device device) throws IOException {
//        WebSocketSession session = allClients.get(device.getDevicesuuid());
//        if (session != null && session.isOpen()) {
//            device.setStatus("1");//设置在线
//        }
//    }

    public abstract boolean isNeedUpdateStatus();
}
